GraphQL(2) - GraphQL & Apollo client


Posted by TempuraEngineer on 2022-10-31

目錄


Apollo client是甚麼

Apollo client是一個狀態管理用的package,常用於前端為React,後端為GraphQL的專案

透過它可以和本地、遠端的GraphQL API互動

前一篇提到的GraphQL cache問題也以透過它處理


基本使用

  1. 安裝

     npm i @apollo/client
    
     // 用於解析GraphQL的query
     npm i graphql
    
  2. 初始化Apollo client

     // ApolloClient用於建立連線
     // InMemoryCache用於快取住fetch下來的資料
     // ApolloProvider是用於包裹React app的組件
     // gql是一個函式,參數是query string
     import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
    
     // 建立連線
     const client = new ApolloClient({
       // API網址,又稱作endpoint
       uri: 'https://flyby-gateway.herokuapp.com/', 
       cache: new InMemoryCache(),
     });
    
  3. query

     client.query({
         query:gql`{
             locations {
                 photo
                 name
             } 
         }`
     }).then(d => console.log(d)); 
    
     // 或者打開dev tool的Network就能看到playload看response
     // response會有撈出來的資料
    
  4. 把client用到React

    使用ApolloProvider組件,可以連接Apollo client和React,其功能類似React的Context.Provider

    使用ApolloProvider後它會取代context,在它底下的組件都可以連到Apollo client,因此官方推薦把它放在index.js

     // index.js
     import React from 'react';
     import ReactDOM from 'react-dom/client';
     import './style/index.css';
     import App from './App'; 
    
     import { ApolloClient, InMemoryCache, ApolloProvider } from     '@apollo/client';
    
     const client = new ApolloClient({
       uri: 'https://flyby-gateway.herokuapp.com/', // API網址
       cache: new InMemoryCache(),
     });
    
     const root = ReactDOM.createRoot(document.getElementById('root'));
    
     root.render(
         <ApolloProvider client={client}>
             <App />
         </ApolloProvider>
     );
    
  5. 使用useQuery撈資料

    useQuery是一個React hook,它會回傳loading、error狀態、data

    也可以設定onCompelete、onError時呼叫哪個方法、做什麼

    Apollo client撈到資料後會自動把它快取住,這樣下次撈同樣的資料時就會更快

     // ApolloTimeline.js
    
     import { useQuery, gql } from '@apollo/client';
    
     export default function ApolloTimeline(props){
       const { loading, error, data } = useQuery(gql`
         {
           histories(sort: "event_date_utc") {
             event_date_utc
             title
             details
             flight {
               launch_success
             }          
           }
         }
       `);
    
       if (loading) return <p>loading...</p>;
       if (error) return <p>Error :(</p>;
    
       return (
         <div>
           <Timeline position="alternate">
             {
               data.histories.map((i, index) => (
                 <TimelineItem key={index}>
                   <TimelineOppositeContent>    {timeFormatter(i.event_date_utc)}</TimelineOppositeContent>
                   <TimelineSeparator>
                     <TimelineDot color="secondary" />
                     {index === data.histories.length - 1? null : <TimelineConnector />}
                   </TimelineSeparator>
                   <TimelineContent>
                     <Chip label={i.title} color={i.flight?.launch_success? 'success' : 'default'}></Chip>
                   </TimelineContent>
                 </TimelineItem>
               ))
             }
           </Timeline>
         </div>
       )
     }
    


refetch & variable

refetch常用於根據使用者的動作撈資料的情況,例如切換分頁

// ApolloRefetch.js

import { useQuery, gql } from '@apollo/client';

import Chip from '@mui/material/Chip';
import Paper from '@mui/material/Paper';
import CircularProgress from '@mui/material/CircularProgress';

function timeFormatter(str){
  if(!str) return '';
  return str.replace(/T|Z/g, ' ');
}

export default function ApolloRefetch(props){
  const queryMultiple = () => {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    const res1 = useQuery(gql`
    query historyId{
      histories{
        id
      }
    }
  `);
    // eslint-disable-next-line react-hooks/rules-of-hooks
    const res2 = useQuery(gql`
    query history($id:ID!){
      history(id:$id){
        details
        event_date_utc
        flight {
          launch_success
          mission_name
        }
        title
        id
      }
    }
    `, {variables:{id:'1'}});
    return [res1, res2];
  };

  const [
      { loading: isIdLoading, error: isIdError, data: idData },
      { loading, data, refetch }
  ] = queryMultiple();


  if (isIdLoading) return <p>loading...</p>;
  if (isIdError) return <p>Error :(</p>;

  return (
    <div className='h-screen p-2'>
        <div className='flex justify-around'>
          {
              idData?.histories?.map((i, index) => <Chip key={index} 
                                                        color={data.history?.id === i.id? 'info' : 'default'} 
                                                        label={i.id} 
                                                        onClick={() => refetch({id:i.id})}></Chip>)
          }
        </div>


        <Paper elevation={3} sx={{padding:4, margin:4, display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
          {
            loading? <CircularProgress/> : <>
                                            <h1 className='text-xl bold border-b-4 border-double border-zinc-600 mb-4 pb-1'>{data.history?.title}</h1>
                                            <p className='mb-2'>{timeFormatter(data.history?.event_date_utc)}</p>
                                            <p className='mb-2'>{data.history?.details}</p>
                                            <p className='mb-2'>{data.history?.flight?.mission_name}</p>
                                            <p>{data.history?.flight?.launch_success}</p>
                                          </>
          }
        </Paper>
    </div>
  )
}


參考資料

Get started with Apollo Client
Apollo client - Queries
Refetching queries in Apollo Client
Cleaning Unwanted Fields From GraphQL Responses

playground

FlyBy GraphQL API


#graphql #Apollo client







Related Posts

如何使用 Python 實現 LRU Cache 快取置換機制

如何使用 Python 實現 LRU Cache 快取置換機制

JavaScript 五四三 Ep.02 Array.prototype.forEach()

JavaScript 五四三 Ep.02 Array.prototype.forEach()

Linkedin Java 技術認證題庫

Linkedin Java 技術認證題庫


Comments